Skip to content

fix(server): opencode by-id existence fallback — stop declaring rebound panes dead at restart - #579

Merged
danshapiro merged 7 commits into
mainfrom
fix/opencode-existence-fallback
Jul 29, 2026
Merged

fix(server): opencode by-id existence fallback — stop declaring rebound panes dead at restart#579
danshapiro merged 7 commits into
mainfrom
fix/opencode-existence-fallback

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

Summary

At restart, the reconcile existence probe answered from the opencode session LISTING, which filters child sessions (parent_id IS NULL) and directory-less rows. A pane rebound via the opencode TUI-plugin signal lane to a child session id (the session the user was actually viewing) therefore read Absent; because the rebind ledger makes ever_bound true, reconcile derived DeadSession{session_not_on_disk} — the "Dead sessions" dialog — even though the row exists on disk. Worse, a pane claiming the superseded root id was chain-corrected to the invisible child terminus, so one child rebind buried BOTH bookmarks.

The Fix

IndexExistenceProbe now falls back to a direct read-only by-id sqlite query (no parent_id filter, no directory filter) when the warm index says Absent for opencode — the exact analog of the existing claude raw-file fallback, keeping the probe in agreement with the attach arm (opencode --session <id> resolves by id, children included).

Load-bearing error semantics: DB open/read failure answers Unknown (reconcile defers and retries), never Absent — so WAL lock contention can't recreate the bug; missing DB file stays Absent.

Performance: Short 250ms busy timeout (not the listing's 5s) since exists() is sync on the reconcile path.

Fallback hits: Feed the monotone observed-set; claude/codex Absent never consult the opencode DB.

Archived sessions: Count as Present — validated against real opencode (attach to archived sessions succeeds).

Migration: No ledger migration needed: the ledger was recording the truth; fixing the reader heals existing affected users on their next reconcile automatically.

Testing

  • Promoted spike test (child claim → Respawn{child}; stale superseded-root claim → Respawn{child, corrected}; control root unchanged)
  • Probe unit tests mirroring the claude-fallback quartet
  • Directory-less root row test
  • Unreadable-DB → Unknown test
  • Opt-in real-provider contract test (FRESHELL_RUN_REAL_PROVIDER_CONTRACTS=1) run against opencode 1.18.9 — passed

Review Status

Built via the-usual recipe; independent plan review (2 rounds) and delta review (first-round pass, zero blockers) both passed.

Follow-ups (non-blocking)

  • Channel-specific DB filename (opencode-.db / OPENCODE_DB env)
  • More precise error label for permanently-unreadable DB

Generated with Amplifier

…ified the archived-refusal premise

Stage-2 load-bearing validation (11 verified, 1 falsified): live attach to an
archived opencode session succeeds and Session.get has no time_archived filter
(v1.18.9), so the by-id existence query drops 'AND time_archived IS NULL' to
preserve attach parity. Tests flipped (archived => found/Present), the legacy-
schema test repurposed to pin schema robustness, and validation results +
accepted limitations (channel-suffixed db filename) recorded in Global
Constraints. Ledger: .worktrees/.the-usual-logs/opencode-existence-fallback/
load-bearing-ledger.md
Independent review found Task 4's load-bearing assertion could not pass:
waitForOpencodeDbSession returns only {id, title, directory}, so
childRow.parent_id was always undefined and the premise gate's child
verification would fail for the wrong reason (or, via the old adapt
hedge, be silently dropped and pass vacuously against a ROOT session).

- Extend Task 4 to also modify the harness: queryOpencodeSessionRow now
  selects parent_id (additive; existing callers unaffected), declared in
  the task's Files list and Step 5 commit command.
- Assertion is now expect(childRow.parent_id).toBe(rootSessionId), marked
  LOAD-BEARING; adapt hedge rewritten to forbid weakening it (PRAGMA
  table_info fallback for column-name variance; no-parent-column case
  routes to the existing recorded-infeasibility branch).
@danshapiro
danshapiro merged commit 4c04dc9 into main Jul 29, 2026
3 checks passed
@danshapiro
danshapiro deleted the fix/opencode-existence-fallback branch July 29, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant